/*
 * GeoVISTA Center (Penn State, Dept. of Geography)
 * Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
 * Licensed under Simplified BSD License
 *
 *
 * @author: jin Chen 
 * @date: May 28, 2005$
 * @version: 1.0
 */
package edu.psu.geovista.app.hce.clustering.hierarchical;

import edu.psu.geovista.app.hce.clustering.OrderingCluster;
import edu.psu.geovista.app.hce.clustering.hierarchical.linkage.LinkageMethod;
import edu.psu.geovista.app.hce.clustering.hierarchical.linkage.SingleLink;
import edu.psu.geovista.app.hce.clustering.matrix.DfClusterDialogMatrixFloat;
import edu.psu.geovista.app.hce.clustering.matrix.DialogMatrixFloat;
import edu.psu.geovista.app.hce.clustering.model.BCluster;
import edu.psu.geovista.app.hce.clustering.model.DfBCluster;
import edu.psu.geovista.app.hce.clustering.model.DfLCluster;
import edu.psu.geovista.app.hce.clustering.model.LCluster;
import edu.psu.geovista.app.hce.clustering.similarity.RowSimilarityWorker;
import edu.psu.geovista.app.hce.clustering.similarity.SimilarityWorker;
import edu.psu.geovista.app.hce.clustering.similarity.attribute.SimilarityComparator;
import edu.psu.geovista.app.vit.pub.utils.VitUtils;
import edu.psu.geovista.common.utils.collection.CollectionUtils;
import edu.psu.geovista.data.model.AppDataModel;

import java.awt.*;
import java.util.*;
import java.util.List;

//import ;

public class AgglomerativeClusteringpub implements OrderingCluster {
     private static final boolean MDEBUG=true;

    AppDataModel data;

    private SimilarityWorker simworker;//=new RowSimilarityWorker();
    DialogMatrixFloat originalSimMatrix;// 
    DfClusterDialogMatrixFloat simMatrix; // 


    BinaryClusterArranger arranger;//=new NumberClusterArranger() ;
    LinkageMethod linkage;//=new SingleLink();
    int nbCluster;
    int nbLCluster;
    List <DfLCluster> leafs;  // 
    List clusters;   // 
    BCluster root ;

    public AgglomerativeClusteringpub() {
        simworker=new RowSimilarityWorker();
        arranger=new NumberClusterArranger() ;
        linkage=new SingleLink();
    }
     public AgglomerativeClusteringpub(SimilarityComparator similarityAlgorithm){
         this();
         simworker.setSimMethod(similarityAlgorithm);
     }

    public void setData(AppDataModel data) {
        this.data = data;
    }
    public  void clustering(AppDataModel data, SimilarityComparator similarityAlgorithm){
         simworker.setSimMethod(similarityAlgorithm);
          clustering(data);
    }
    /**
     *  
     * @param data
     * @return
     */
    public  void clustering(AppDataModel data){



        originalSimMatrix = simworker.buildOnNormalData(data);//buildMatrix(data);


        //copy matrix
         long startcopymatrix=0;
         long endcopymatrix=0;

        ArrayList matrixData = (ArrayList) originalSimMatrix.getMatrixData();
        List copyMatrix=(List) matrixData.clone();   // 
        Collections.copy(copyMatrix,matrixData);

        simMatrix=new DfClusterDialogMatrixFloat(copyMatrix);





        //generate clusters
        long startcluster=0;
        long endcluster=0;

        //int numRecords = data.getNumRecords();// 
        //initClusters(numRecords);
        simworker.initClusters(simMatrix,data);
        root = doCluster();


        process(root);

        for (Iterator iterator = leafs.iterator(); iterator.hasNext();) {
            LCluster lf = (LCluster) iterator.next();
            int attrIndex = lf.getDtIndex();
            String rowname = data.getRowName(attrIndex);
            lf.setId(rowname);

        }
        valueOrder(root,data,null,true);
    }
    public static void valueOrder(BCluster cluster, AppDataModel datamodel, float[] weights, boolean ascend){
          // if weight ==null, all the variables are considered
          if(weights==null||weights.length ==0){
              int colCount = datamodel.getColumnCount();
              weights=new float[colCount];
              Arrays.fill(weights,1);
          }

          if(! (cluster instanceof LCluster)){//no need to draw leaf cluster
               BCluster left = cluster.getCls1();//getLeft();
               BCluster right = cluster.getCls2();//.getRight();
              valueOrder(left,datamodel,weights,ascend);
              valueOrder(right,datamodel,weights,ascend);

              Vector avgVector = getMeanVector(left, right);
              ((DfBCluster)cluster).setAttrVector(avgVector);

              double diff = compare(left, right);

              if(diff<0){ // left < right
                  if(ascend){  // small ... large
                      //do nothing,
                  }
                  else{
                      swap(cluster);
                  }
              }
              else if (diff>0){ // left > right
                  if(ascend){  // small ... large
                      swap(cluster);
                  }
                  else{
                     //do nothing,
                  }
              }
              else{// the two children equal. do nothing,
              }


          }
          else{
             LCluster cls= (LCluster) cluster;

              //cal the data vector for the leaf
              int dataindex = cls.getDtIndex();
              double[] rowvalues = datamodel.getRowValueAsDouble(dataindex);
              Vector row=new Vector(rowvalues.length );
              double val=0;
              for (int i=0;i<rowvalues.length ;i++){
                  val=rowvalues[i]*weights[i];
                  row.add(val);
              }
              cls.setAttrVector(row);
          }
    }
     public  static double compare(BCluster c1, BCluster c2){
        Vector v1 = c1.getAttrVector();
              Vector v2 = c2.getAttrVector();
        double[] d1 = CollectionUtils.convert2DoubleArray(v1);
        double[] d2 = CollectionUtils.convert2DoubleArray(v2);
        double sum=0;
        for (int i=0;i<d1.length ;i++){
            sum=sum+(d1[i]-d2[i]);
        }
        return sum;
    }
    public   static void swap(BCluster cluster){
           BCluster left = cluster.getCls1();
           BCluster right = cluster.getCls2();

           Set leftIds = left.getDataIndexSet();
           Set rightIds = right.getDataIndexSet();
           // 
           cluster.setCls1(right);
          // cluster.setLeftDataIndexs(leftIds);
           cluster.setCls1(left);
           //cluster.setRightDataIndexs(rightIds);

       }

    public static Vector getMeanVector(BCluster c1, BCluster c2){
           Vector v1 = c1.getAttrVector();
           Vector v2 = c2.getAttrVector();
           double[] d1 = CollectionUtils.convert2DoubleArray(v1);
           double[] d2 = CollectionUtils.convert2DoubleArray(v2);
           int count1 = c1.getDataIndexSet().size();
           int count2 = c2.getDataIndexSet().size();
           double[] avg = getMeanVector(d1, d2, count1, count2);
           return CollectionUtils.convertDoubleToVector(avg);

       }

      public static double[] getMeanVector(double[] v1, double[] v2, int count1, int count2) {
        double w1=count1*1.0/(count1+count2);
        double w2=count2*1.0/(count1+count2);

        double avg[]=new double[v1.length ];
        for (int i=0;i<avg.length ;i++){
            avg[i]=v1[i]*w1+v2[i]*w2;
        }
        return avg;
    }
     private  void process(BCluster cluster){
        if(clusters==null)clusters=new ArrayList();
        if(leafs==null)leafs=new ArrayList<DfLCluster>();
        if (!(cluster instanceof LCluster)){
            BCluster left = cluster.getCls1(); //
            process(left);

            cluster.setIndex(nbCluster++);
            clusters.add(cluster);
            BCluster right = cluster.getCls2();
            process(right);

        }
        else{
            cluster.setIndex(nbCluster++);
            ((LCluster)cluster).setLfindex( nbLCluster++);

            leafs.add((DfLCluster) cluster);
            clusters.add(cluster);
        }
    }

    public BCluster doCluster(){

        List clusters = simMatrix.getClusters();
        int nCluster=clusters.size();
        int level=0;
        while(nCluster >1){
            Point mostSimilar = simMatrix.findMax();//findMax();  // 

           
            if(mostSimilar.x<0){
                //System.out.println("");
                nCluster--;
                continue;
            }

         
            float similarity = simMatrix.get(mostSimilar.x,mostSimilar.y);
            //Debug.debug(DEBUG, "find most similar"+mostSimilar.toString() );
            BCluster cluster1 = (BCluster) clusters.get(mostSimilar.x );
            BCluster cluster2 = (BCluster) clusters.get(mostSimilar.y );
            BCluster merged = generateCluster(cluster1, cluster2, similarity);
            try {
                simMatrix.delete(new int[]{mostSimilar.x ,mostSimilar.y});//it will the 2 rows in matrix as well as 2 clusters in the clusterList
            } catch (Exception e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }

            float[] sims = getSim(clusters,merged);

            
            // 
            simMatrix.add(sims);

           

            clusters.add(merged);
            

           

            nCluster=clusters.size() ;
        }
        assert (clusters.size() ==1);
        return     (BCluster) clusters.get(0);

    }




    private BCluster generateCluster(BCluster cluster1, BCluster cluster2, float similarity) {
        BCluster[] orderedClusters = arranger.arrange(cluster1,cluster2); // 
        BCluster mg =new DfBCluster();
        mg.setCls1(orderedClusters[0]);// 
        mg.setCls2(orderedClusters[1]);// 

        //return null;  //calculate
        int[] Lids = cluster1.getIds();
        int[] Rids = cluster2.getIds();


        int[] ids = VitUtils.add(Lids,Rids);
        mg.setIds(ids);
       // cluster1.setIds(null); // 
       // cluster2.setIds(null);
        mg.setSimilarity(similarity);
        // 
        if(cluster1 instanceof LCluster){
            cluster1.setSimilarity(similarity);
        }
        if(cluster2 instanceof LCluster){
            cluster2.setSimilarity(similarity);
        }
        return mg;
    }


    /**
     *  
     * @param clusters
     * @param cluster
     * @return
     */
    private float[] getSim(List clusters, BCluster cluster) {

        linkage.setSimMatrix(this.originalSimMatrix );
        int size=clusters.size() +1;
        float[] sims=new float[size];
        int i=0;
        for (Iterator iterator = clusters.iterator(); iterator.hasNext();) {
            BCluster cls = (BCluster) iterator.next();
            double sim = linkage.similarity(cls,cluster);
            sims[i++]=(float) sim;

        }
        sims[i]=1; //last one always =1;
        return sims;

    }

    public DialogMatrixFloat getOriginalSimMatrix() {
        return originalSimMatrix;
    }

    /*******************************************************************************************************
     *                implementing OrderingCluster
     *******************************************************************************************************/

    public List cluster(AppDataModel datamodel, AppDataModel normaldata) {
        this.clustering(normaldata);
        List leafs = getLeafs();
        return leafs;

    }
     public void setSimworker(SimilarityWorker simworker) {
        this.simworker = simworker;
    }

    public List getClusters() {
        return clusters;
    }

    public List<DfLCluster> getLeafs() {
        return leafs;
    }

    public int getNbLCluster() {
        return nbLCluster;
    }

    public int getNbCluster() {
        return nbCluster;
    }
    public String[] getLeafIds(){
         String[] ids=new String[leafs.size() ];
         int i=0;
         for (Iterator iterator = leafs.iterator(); iterator.hasNext();) {
            LCluster lfc = (LCluster) iterator.next();
             String dataId = lfc.getId();
             ids[i++]=dataId;
         }
        return ids;
    }

    public BCluster getRoot() {
        return root;
    }
    /**
     *
     * @return 
     */
    public double[] getMaxSimilarity(){
        float maxsim =0;
        int clusterIndex=0;
        for (Iterator iterator = clusters.iterator(); iterator.hasNext();) {
            BCluster binaryCluster = (BCluster) iterator.next();
            float similarity = binaryCluster.getSimilarity();
            if(similarity>maxsim){
                maxsim =similarity;
                clusterIndex=binaryCluster.getIndex();
            }

        }
        return new double[]{clusterIndex, maxsim};
    }

    public double getMinSimilarity(){
        return this.getRoot().getSimilarity() ;
    }
}
